// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#pragma once

#include <functional>
#include <mutex>
#include <vector>

namespace kumo::pollux {
    /// This template class is a lock wrapper which is similar to std::lock_guard
    /// and additionally, it allows user to add deferred callbacks to invoke right
    /// after the lock release. 'LockType' class needs to provides lock()/unlock()
    /// methods to acquire and release lock. Note that the class always calls
    /// unlock() in object destructor even if lock() calls throws in constructor.
    ///
    /// The following is the code snippets to show how to
    /// use it:
    ///    std::mutex mu;
    ///    {
    ///       ScopedLock sl(&mu);
    ///       sl.addCallback([&]() {
    ///          std::lock_guard l(mu);
    ///          printf("acquired lock again"); });
    ///    }
    template<typename LockType>
    class ScopedLock {
    public:
        ScopedLock(LockType *lock) : lock_(lock) {
            lock_->lock();
        }

        ~ScopedLock() {
            lock_->unlock();
            for (auto cb: callbacks_) {
                cb();
            }
        }

        using Callback = std::function<void()>;

        /// Invoked to add 'cb' to run after release the lock on object destruction.
        void addCallback(Callback &&cb) {
            callbacks_.push_back(std::move(cb));
        }

    private:
        LockType *const lock_;
        std::vector<Callback> callbacks_;
    };
} // namespace kumo::pollux
